//import UnityEngine;

// AniMate animation helper class for Unity3D
// Version 1.5 - 24. February 2009
// Copyright (C) 2009  Adrian Stutz
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License : published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY || FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, see <http://www.gnu.org/licenses/>.

import System.Reflection;
import System.Reflection.Emit;
//import System.Collections;


// ---------------------------------------- //
// ANIMATION HELPER CLASS

enum AniType
{
	To,
	From,
	By
};

class Ani extends MonoBehaviour
{ 
	
	// ---------------------------------------- //
	// CONFIGURATION PROPERTIES
	
	// Default delay
	var defaultDelay : float = 0;
	// Default physics behaviour
	var defaultPhysics : boolean = false;
	// Default callback
	//public defaultCallback : callable = null
	// Default easing
	var defaultEasing : System.Type = LinearEasing;
	// Default easing direction
	var defaultDirection : EasingType = EasingType.In;
	// Default animation drive
	var defaultDrive : System.Type = RegularDrive;
	// Default frames per second (-1 for fullspeed)
	var defaultFps : float = -1;
	// Remove existing animations for property
	var defaultReplace : boolean = false;
	
	// ---------------------------------------- //
	// INTERNAL FIELDS
	
	// ArrayList with
	// 0: AniValue object
	// 1: AniMator object
	// 3: callback
	// 4: fps
	// 5: time since last frame (for fps)
	private var animations : ArrayList = ArrayList();
	private var fixedAnimations : ArrayList = ArrayList();
	
	// ---------------------------------------- //
	// SINGLETON
	
	// Singleton instance
	static function get Mate() : Ani
	{
	//	get:
			// Create instance if none exists yet
			if( _mate == null )
			{
				// Create GameObject to attach to
				var go = GameObject("AniMate");
				// Attach Ani to GameObject
				_mate = go.AddComponent(Ani);
            }
			return _mate;
	   
	}
	static var _mate : Ani;
	
	// Save instance
	function Awake()
	{
		Ani._mate = this;
    }
	
	// ---------------------------------------- //
	// CREATE NEW ANIMATION
	
	function To (obj : System.Object, duration : float, properties : Hashtable)
	{
		properties = properties.Clone();
		// Fill options with defaults
		options = ExtractOptions(properties);
		// Add to animations
		CreateAnimations(obj, properties, duration, options, AniType.To);
		// Return yield to chain animations
		return WaitForSeconds(duration);
    }
	
	//function To (obj : System.Object, duration : float, properties : Hashtable)
	//{
	//	To (obj, duration, Hash(properties));
    //}
	
	function To (obj : System.Object, duration : float, properties : Hashtable, options : Hashtable)
	{
		properties = properties.Clone();
		options = options.Clone();
		// Fill options with defaults
		options = ExtractOptions(options);
		// Add to animations
		CreateAnimations(obj, properties, duration, options, AniType.To);
		// Return yield to chain animations
		return WaitForSeconds(duration);
    }
	
	//function To (obj : System.Object, duration : float, properties : Hashtable, options : Hashtable)
	//{
	//	To (obj, duration, Hash(properties), Hash(options));
    //}
	
	function From (obj : System.Object, duration : float, properties : Hashtable)
	{
		properties = properties.Clone();
		// Fill options with defaults
		options = ExtractOptions(properties);
		// Add to animations
		CreateAnimations(obj, properties, duration, options, AniType.From);
		// Return yield to chain animations
		return WaitForSeconds(duration);
    }
	
	//function From (obj : System.Object, duration : float, properties : Hashtable)
	//{
	//	From (obj, duration, Hash(properties));
    //}
	
	function From (obj : System.Object, duration : float, properties : Hashtable, options : Hashtable)
	{
		properties = properties.Clone();
		options = options.Clone();
		// Fill options with defaults
		options = ExtractOptions(options);
		// Add to animations
		CreateAnimations(obj, properties, duration, options, AniType.From);
		// Return yield to chain animations
		return WaitForSeconds(duration);
    }
	
	//function From (obj : System.Object, duration : float, properties : Hashtable, options : Hashtable)
	//{
	//	From (obj, duration, Hash(properties), Hash(options));
    //}
	
	function By (obj : System.Object, duration : float, properties : Hashtable)
	{
		properties = properties.Clone();
		// Fill options with defaults
		options = ExtractOptions(properties);
		// Add to animations
		CreateAnimations(obj, properties, duration, options, AniType.By);
		// Return yield to chain animations
		return WaitForSeconds(duration);
    }
	
	//function By (obj : System.Object, duration : float, properties : Hashtable)
	//{
	//	By (obj, duration, Hash(properties));
    //}
	
	function By (obj : System.Object, duration : float, properties : Hashtable, options : Hashtable)
	{
		properties = properties.Clone();
		options = options.Clone();
		// Fill options with defaults
		options = ExtractOptions(options);
		// Add to animations
		CreateAnimations(obj, properties, duration, options, AniType.By);
		// Return yield to chain animations
		return WaitForSeconds(duration);
    }
	
	//function By (obj : System.Object, duration : float, properties : Hashtable, options : Hashtable)
	//{
	//	By (obj, duration, Hash(properties), Hash(options));
    //}
	
	// ---------------------------------------- //
	// MANAGE ANIMATIONS
	
	// Number of all aniamtions
	function Count()
	{
		return (animations.Count + fixedAnimations.Count);
    }		
	
	// Check if an animation exists for object
	function Has(obj : System.Object)
	{
		return (Contains(obj,null,animations) || Contains(obj,null,fixedAnimations));
    }
	
	// Check if animation exists for object && proeperty
	function Has(obj : System.Object, name  : System.String)
	{
		return (Contains(obj,name,animations) || Contains(obj,name,fixedAnimations));
    }
	
	// Check for object && property
	private function Contains(obj : System.Object, name  : System.String, anims : ArrayList)
	{
		for( var anim : ArrayList in anims )
		{
			var val : AniValue = anim[0];
			if ((name == null && val.Is(obj))
					||
				(name != null && val.Is(obj,name)))
				{
				return true;
				}
        }
		return false;
    }
	
	// Stop all animations of an object
	function StopAll(obj : System.Object)
	{
		// Regular Animations
		Remove(obj, null, animations);
		// Fixed Animations
		Remove(obj, null, fixedAnimations);
    }
	
	// Stop all animations of an object for a property
	function Stop(obj : System.Object, name  : System.String)
	{
		// Regular Animations
		Remove(obj, name, animations);
		// Fixed Animations
		Remove(obj, name, fixedAnimations);
    }
	
	// Remove animations
	private function Remove(obj : System.Object, name  : System.String, anims : ArrayList)
	{
		var remove = ArrayList();
		for( var anim : ArrayList in anims )
		{
			var val : AniValue = anim[0];
			if ((name == null && val.Is(obj))
					||
				(name != null && val.Is(obj,name)))
				{
				remove.Add(anim);
				}
        }
		for( anim in remove )
		{
			animations.Remove(anim);
        }
    }
	
	// ---------------------------------------- //
	// MAIN ANIMATION LOOPS
	
	private function DoAnimation( anims : ArrayList)
	{
		var finished = ArrayList();
		// Loop through animations
		for( var anim : ArrayList in anims )
		{
			var val : AniValue = anim[0];
			var mat : AniMator = anim[1];
			var callback : System.Object = null; //anim[2] : callable
			var spf : float = anim[3];
			// Check if animation has started
			if( !mat.Running() )
				continue;
			// Honor seconds per frame
			if( spf > 0 )
			{
				var timesince : float = anim[4];
				timesince += Time.deltaTime;
				// Not yet time, skip
				if( timesince < spf )
				{
					anim[4] = timesince;
					continue;
				}
				// Update this frame				
				else
				{
					anim[4] = timesince % spf;
				}
            }
			// Animate || call calback with value
			if (callback == null)
				val.Set(mat.GetValue());
			/*else
				callback(mat.GetValue());*/
			// Check if finished
			if( mat.Finished() )
				finished.Add(anim);
        }
        
		// Remove finished animations
		for( var fin in finished )
			anims.Remove(fin);
			
    }
	
	// Regular animations
	function Update()
	{
		DoAnimation(animations);
    }
	
	// Physics animations
	function FixedUpdate()
    {
		DoAnimation(fixedAnimations);
    }
	
	// ---------------------------------------- //
	// INTERNAL METHODS
	
	// Exctract options for Hash && fill defaults where needed
	private function ExtractOptions(options : Hashtable)
	{
		var exct = {};
		// Delay
		if (options["delay"] == null)		
		{
			exct["delay"] = defaultDelay;
        }
		else
		{
		    var tmpFloat : float = options["delay"];
			exct["delay"] = tmpFloat;
			options.Remove("delay");
        }
		// Physics
		if (options["physics"] == null)
		{
			exct["physics"] = defaultPhysics;
        }
		else
		{
		    var tmpBoolean : boolean = options["physics"];
			exct["physics"] = tmpBoolean;
			options.Remove("physics");
        }
		// Callback
		if (options["callback"] == null)
		{
			//exct["callback"] = defaultCallback;  
        }
		else
		{
			//exct["callback"] = cast(callable,options["callback"]);
			options.Remove("callback");
        }
		// Easing
		if (options["easing"] == null)
		{
			exct["easing"] = defaultEasing;
        }
		else
		{
		    var tmpType : System.Type = options["easing"];
			exct["easing"] = tmpType;
			options.Remove("easing");
        }
		// Easing Direction
		if (options["direction"] == null)
		{
			exct["direction"] = defaultDirection;
        }
		else
		{
		    var tmpEasingType : EasingType = options["direction"];
			exct["direction"] = tmpEasingType;
			options.Remove("direction");
        }
		// Animation drive
		if (options["drive"] == null)
		{
			exct["drive"] = defaultDrive;
        }
		else
		{
		    tmpType = options["drive"];
			exct["drive"] = tmpType;
			options.Remove("drive");
        }
		// Animation drive
		if (options["rigidbody"] == null)
		{
			exct["rigidbody"] = null;
        }
		else
		{
		    var tmpRigidbody : Rigidbody = options["rigidbody"];
			exct["rigidbody"] = tmpRigidbody;
			options.Remove("rigidbody");
        }
		// Fps (saved : seconds per frame)
		if (options["fps"] == null)
		{
			exct["fps"] = 1 / defaultFps;
        }
		else
		{
		    var tmpFPS : float = options["fps"];
			exct["fps"] = 1 / tmpFPS;
			options.Remove("fps");
        }
		// Replace animation on property
		if (options["replace"] == null)
		{
			exct["replace"] = defaultReplace;
        }
		else
		{
		    tmpBoolean = options["replace"];
			exct["replace"] = tmpBoolean;
			options.Remove("replace");
        }
		// Return hash with all values
		return exct;
    }
	
	// Extract animation properties from Hash
	private function CreateAnimations(obj : System.Object, properties : Hashtable, duration : float, options : Hashtable, type : AniType)    
	{
		for( var item : DictionaryEntry in properties )
		{
			// Extract name && value
			var tmpString : System.String = item.Key;
			name = tmpString;
			var value : System.Object = item.Value;
			// Create value object
			aniv = AniValue(obj,name);
			// Get current value
			current = aniv.Get();
			// Setup variables
			if (type == AniType.To)
			{
				start = current;
				target = value;
            }
			else if(type == AniType.From)
			{
				start = value;
				target = current;
            }
			else if(type == AniType.By)
			{
				start = current;
				diff = value;
            }
			// Calculate difference for To && From
			if ((type == AniType.To || type == AniType.From)
					&& DriveNeedsDiff(options["drive"]))
            {
				try
				{
					diff = LinearAdd( target, -1.0, start );
					test = LinearAdd( start, 0.1, diff ); 
				}
				catch( e : System.Exception )
				{
					throw System.Exception("Cannot animate "+aniv.ValueType()+" with target "+value.GetType()+": Operation +, - || * not supported.");
				}
            }
            
			// Create animation object
			mat = AniMator(start, target, diff, duration, options["delay"], options["easing"], options["direction"], options["drive"]);
			// Remove existing animations
			if (options["replace"])
				Stop(obj,name);
			// Add to animations
			if (options["physics"] == false && options["rigidbody"] == null)
			{
				// Regular animation
				var anim = new ArrayList();
				anim.Add( aniv );
				anim.Add( mat );
				anim.Add( options["callback"] );
				anim.Add( options["fps"] );
				anim.Add( 0 );
				animations.Add( anim );
            }
			else
			{
			/*
				// Rigidbody animation
				callback : callable;
				if options["rigidbody"] != null && name == "position":
					callback = (options["rigidbody"] : Rigidbody).MovePosition;
				elifs["rigidbody"] != null && name == "rotation":
					callback = (options["rigidbody"] : Rigidbody).MoveRotation;
				// Other callback
				else:
					callback = options["callback"];
				// Physics animation
				fixedAnimations.Add([aniv,mat,callback,options["fps"], 0]); */
            }
			// From: Set to starting value
			if (type == AniType.From)
				aniv.Set(start);
        }
    }
	
	function DriveNeedsDiff(drive : System.Type) : boolean
	{
		var d : AnimationDrive = drive();
		return d.CalculateDiff();
    }
}
	

// ---------------------------------------- //
// WRAPPER FOR A SINGLE VALUE
	
class AniValue
{
	
	// ---------------------------------------- //
	// CONFIGURATION
	
	static var bFlags : BindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
	
	static function ReflectionFieldSetHandler(source : System.Object, value : System.Object)
	{
	}

	//var delegate SetHandler(source : System.Object, value : System.Object);
	var SetHandler = ReflectionFieldSetHandler;
		
	// ---------------------------------------- //
	// PRIVATE FIELDS
	
	// Object a field || property is animated on
	var obj : System.Object;
	// Name of the field || property
	var name  : System.String;
	
	// Type object
	var objType : System.Type;
	// FieldInfo object
	var fieldInfo : FieldInfo;
	// PropertyInfo object
	var propertyInfo : PropertyInfo;
	
	var setter = null; // ReflectionFieldSetHandler;
	
	// ---------------------------------------- //
	// CONSTRUCTOR
	
	function AniValue(o : System.Object, n  : System.String)
	{
		// Save
		obj = o;
		name = n;
		// Get info objects
		objType = obj.GetType();
		// Get field || property info
		fieldInfo = objType.GetField(n, AniValue.bFlags);
		propertyInfo = objType.GetProperty(n, AniValue.bFlags);
		// Check info objects
		if (fieldInfo == null && propertyInfo == null)
			throw System.MissingMethodException("Property || field '"+n+"' not found on "+obj);
		// Create dynamic method
		/*
		if (fieldInfo != null )
			setter = FieldSetMethod(objType, fieldInfo);
		else
			setter = PropertySetMethod(objType, propertyInfo);
			*/
    }
	
	// ---------------------------------------- //
	// UTILITY METHODS
	
	// Get type of field/property for debug purposes
	function ValueType()
	{
		if (propertyInfo != null)
			return propertyInfo.PropertyType;
		else
			return fieldInfo.FieldType;
    }
	
	// Check if AniValue is from given object
	function Is(checkObj : System.Object)
	{
		return (obj == checkObj);
    }
	
	// Check if AniValue is from given object && value
	function Is(checkObject : System.Object, checkName  : System.String)
	{
		return (Is(checkObject) && checkName == name);
    }
	
	// Create a dynamic set method for a PropertyInfo
	function PropertySetMethod(type : System.Type, pInfo : PropertyInfo) 
	{
	/*
		setMethodInfo = pInfo.GetSetMethod(true);
		dynamicSet = DynamicMethod("SetHandler", System.Void.Type, [System.Object.Type, System.Object.Type], type, true);
		setGenerator = dynamicSet.GetILGenerator();
		
		setGenerator.Emit(OpCodes.Ldarg_0);
		setGenerator.Emit(OpCodes.Ldarg_1);
		if (setMethodInfo.GetParameters()[0].ParameterType.IsValueType)
			setGenerator.Emit(OpCodes.Unbox_Any, setMethodInfo.GetParameters()[0].ParameterType);
		setGenerator.Emit(OpCodes.Call, setMethodInfo);
		setGenerator.Emit(OpCodes.Ret);
		
		return dynamicSet.CreateDelegate(SetHandler);//: SetHandler
		*/
    }
	
	// Create a dynamic set method for a FieldInfo
	function FieldSetMethod(type : System.Type, fInfo : FieldInfo)
	{
	/*
		dynamicSet = System.Reflection.Emit.DynamicMethod("SetHandler", System.Void, [System.Object, System.Object], type, true);
		setGenerator = dynamicSet.GetILGenerator();
		
		setGenerator.Emit(OpCodes.Ldarg_0);
		setGenerator.Emit(OpCodes.Ldarg_1);
		if (fInfo.FieldType.IsValueType)
			setGenerator.Emit(OpCodes.Unbox_Any, fInfo.FieldType);
		setGenerator.Emit(OpCodes.Stfld, fInfo);
		setGenerator.Emit(OpCodes.Ret);
		
		return dynamicSet.CreateDelegate(SetHandler);// : SetHandler*/
    }
	
	// ---------------------------------------- //
	// GET AND SET VALUE
	
	// Get field || property
	function Get()
	{
		if (propertyInfo != null)
		{
			return propertyInfo.GetValue(obj, null);
        }
		else
		{
			return fieldInfo.GetValue(obj);
        }
	}
	
	// Set field || property
	function Set(value)
	{
		if( setter != null )
			setter = null; /*setter(obj, value);*/
		else if (propertyInfo != null)
			propertyInfo.SetValue(obj, value, null);
		else
			fieldInfo.SetValue(obj, value);
    }
}

// ---------------------------------------- //
// ANIMATOR CLASS

class AniMator
{
	
	// Initial value
	var startValue : System.Object;
	// End value
	var endValue : System.Object;
	// Change over duration
	var change : System.Object;
	
	var startValueVector3 : Vector3;
	var changeVector3 : Vector3;
	
	// Time of animation start
	var startTime : float;
	// Length of animation
	var duration : float;
	// Easing class
	var easing : AnimationEasing;
	// Easing type
	var easingType : EasingType;
	// Animation drive
	var drive : AnimationDrive;
	
	// Fallback with dynamic typing
	function AniMator(sta : System.Object, end : System.Object, chg : System.Object, dur : float, delay : float, eas : System.Type, typ : EasingType, d : System.Type)
	{
		startValue = sta;
		endValue = end;
		change = chg;
		Setup(dur, delay, eas, typ, d);
    }
	
	// Create Animator
	private function Setup(dur : float, delay : float, eas : System.Type, typ : EasingType, d : System.Type)
	{
		startTime = Time.time + delay;
		duration = dur;
		easing = eas();
		easingType = typ;
		drive = d();
    }
	
	// Get easing with correct type
	function GetEasing(time : float)
	{
		if( easingType == EasingType.In )
			return easing.In(time);
		else if( easingType == EasingType.Out )
			return easing.Out(time);
		else if( easingType == EasingType.InOut )
			return easing.InOut(time);
    }
	
	// Get current animation position (from 0 to 1)
	function GetPosition()
	{
		return Mathf.Clamp01((Time.time - startTime) / duration);
    }
	
	// Check if animation is running
	function Running()
	{
		return startTime < Time.time;
    }

	// Check if aniamtion is finished
	function Finished()
	{
		return (startTime + duration) < Time.time;
    }
	
	// Get current animation value
	function GetValue()
	{
		// Get eased position
		easPos = GetEasing(GetPosition());
		// Use drive to calculate value
		return drive.Animate(startValue, endValue, change, easPos * duration, duration);
    }
}
// ---------------------------------------- //
// INTERFACE FOR ANIMATION DRIVES

class AnimationDrive
{
	virtual function Animate(start : System.Object, end : System.Object, diff : System.Object, time : float, duration : float) : System.Object
	{
	}
	virtual function CalculateDiff () : boolean
	{
	}
}

// ---------------------------------------- //
// REGULAR DRIVE

function LinearAdd( start : System.Object, m :float, diff : System.Object ) : Object
{
    var startType = start.GetType();
    
     if (startType != diff.GetType())
    {
        var tmpStartFloat : float = start;
        var tmpDiffFloat : float = diff;
        return tmpStartFloat + m * tmpDiffFloat;
    }
    // short
    else if(startType == short)
    {
        var tmpStartShort : short = start;
        var tmpDiffShort : short = diff;
        return tmpStartShort + m * tmpDiffShort;
    }
    // integer
    else if(startType == int)
    {
        var tmpStartInt : int = start;
        var tmpDiffInt : int = diff;
        return tmpStartInt + m * tmpDiffInt;				
    }
    // long
    else if(startType == long)
    {
        var tmpStartLong : long = start;
        var tmpDiffLong : long = diff;
        return tmpStartLong + m * tmpDiffLong;
    }
    // float
    else if(startType == float)
    {
        tmpStartFloat = start;
        tmpDiffFloat = diff;
        return tmpStartFloat + m * tmpDiffFloat;
    }
    // double
    else if(startType == double)
    {
        var tmpStartDouble : double = start;
        var tmpDiffDouble : double = diff;
        return tmpStartDouble + m * tmpDiffDouble;
    }
    // decimal
    else if(startType == System.Decimal)
    {
        var tmpStartDecimal : System.Decimal = start;
        var tmpDiffDecimal : System.Decimal = diff;
        return tmpStartDecimal + m * tmpDiffDecimal;		
    }
    // --- Unity types
    // Vector2
    else if(startType == Vector2)
    {
        var tmpStartVector2 : Vector2 = start;
        var tmpDiffVector2 : Vector2 = diff;
        return tmpStartVector2 + m * tmpDiffVector2;
    }
    // Vector3
    else if(startType == Vector3)
    {
        var tmpStartVector3 : Vector3 = start;
        var tmpDiffVector3 : Vector3 = diff;
        return tmpStartVector3 + m * tmpDiffVector3;
    }
    // Vector3
    else if(startType == Vector4)
    {
        var tmpStartVector4 : Vector4 = start;
        var tmpDiffVector4 : Vector4 = diff;
        return tmpStartVector4 + m * tmpDiffVector4;
    }
    // Color
    else if(startType == Color)
    {
        var tmpStartColor : Color = start;
        var tmpDiffColor : Color = diff;
        return tmpStartColor + m * tmpDiffColor;
    }
    // Dynamic typed fallback
    else
    {
        //return start + easPos * diff;
    }
    return start;
    
}

class RegularDrive extends AnimationDrive
{
	function Animate(start : System.Object, end : System.Object, diff : System.Object, time : float, duration : float) : System.Object
	{
		// Positon
		easPos = time / duration;
		// Cast to known types for performance
		startType = start.GetType();
		// --- Builtin types
		// Not matching start && change
		if (startType != diff.GetType())
		{
            var tmpStartFloat : float = start;
            var tmpDiffFloat : float = diff;
			return tmpStartFloat + easPos * tmpDiffFloat;
        }
		// short
		else if(startType == short)
		{
            var tmpStartShort : short = start;
            var tmpDiffShort : short = diff;
			return tmpStartShort + easPos * tmpDiffShort;
        }
		// integer
		else if(startType == int)
		{
            var tmpStartInt : int = start;
            var tmpDiffInt : int = diff;
			return tmpStartInt + easPos * tmpDiffInt;				
        }
		// long
		else if(startType == long)
        {
            var tmpStartLong : long = start;
            var tmpDiffLong : long = diff;
			return tmpStartLong + easPos * tmpDiffLong;
        }
		// float
		else if(startType == float)
		{
            tmpStartFloat = start;
            tmpDiffFloat = diff;
			return tmpStartFloat + easPos * tmpDiffFloat;
        }
		// double
		else if(startType == double)
		{
            var tmpStartDouble : double = start;
            var tmpDiffDouble : double = diff;
			return tmpStartDouble + easPos * tmpDiffDouble;
        }
		// decimal
		else if(startType == System.Decimal)
		{
            var tmpStartDecimal : System.Decimal = start;
            var tmpDiffDecimal : System.Decimal = diff;
			return tmpStartDecimal + easPos * tmpDiffDecimal;		
        }
		// --- Unity types
		// Vector2
		else if(startType == Vector2)
		{
            var tmpStartVector2 : Vector2 = start;
            var tmpDiffVector2 : Vector2 = diff;
			return tmpStartVector2 + easPos * tmpDiffVector2;
        }
		// Vector3
		else if(startType == Vector3)
		{
            var tmpStartVector3 : Vector3 = start;
            var tmpDiffVector3 : Vector3 = diff;
			return tmpStartVector3 + easPos * tmpDiffVector3;
        }
		// Vector3
		else if(startType == Vector4)
		{
            var tmpStartVector4 : Vector4 = start;
            var tmpDiffVector4 : Vector4 = diff;
			return tmpStartVector4 + easPos * tmpDiffVector4;
        }
		// Color
		else if(startType == Color)
		{
            var tmpStartColor : Color = start;
            var tmpDiffColor : Color = diff;
			return tmpStartColor + easPos * tmpDiffColor;
        }
		// Dynamic typed fallback
		else
		{
			//return start + easPos * diff;
        }
    }
	
	function CalculateDiff() : boolean
	{
		return true;
    }
}

// ---------------------------------------- //
// SLERP DRIVE

class SlerpDrive extends AnimationDrive
{
	function Animate(start : System.Object, end : System.Object, diff : System.Object, time : float, duration : float) : System.Object
	{
		return Quaternion.Slerp(start, end, (time / duration));
    }
	function CalculateDiff() : boolean
	{
		return false;
    }
}

// ---------------------------------------- //
// LERP DRIVE

class LerpDrive extends AnimationDrive
{
	function Animate(start : System.Object, end : System.Object, diff : System.Object, time : float, duration : float) : System.Object
	{
		return Vector3.Lerp(start, end, (time / duration));
    }
	function CalculateDiff() : boolean
	{
		return true;
    }
}

// ---------------------------------------- //
// INTERFACE FOR EASING FUNCTIONS

enum EasingType
{
	In,
	Out,
	InOut
};

class AnimationEasing
{
	virtual function In (time : float) : float
	{
	}
	virtual function Out (time : float) : float
	{
	}
	virtual function InOut (time : float) : float
	{
	}
}

class EasingHelper
{
	static function InOut(eas : AnimationEasing, time : float) : float
	{
		if( time <= .5 )
			return eas.In(time * 2) / 2;
		else
			return (eas.Out((time - .5) * 2) / 2) + .5;
    }
}

// ---------------------------------------- //
// LINEAR EASING

class LinearEasing extends AnimationEasing
{
	function In (time : float)
	{
		return time;
    }
	function Out (time : float)
	{
		return time;
    }
	function InOut (time : float)
	{
		return time;
    }
}

// ---------------------------------------- //
// QUADRATIC EASING

class QuadraticEasing extends AnimationEasing
{
	function In (time : float)
	{
		return (time * time);
    }
	function Out (time : float)
	{
		return (time * (time - 2) * -1);
    }
	function InOut (time : float) : float
	{
		return EasingHelper.InOut(this, time);
    }
}

// ---------------------------------------- //
// CUBIC EASING

class CubicEasing extends AnimationEasing
{
	function In (time : float)
	{
		return (time * time * time);
    }
	function Out (time : float)
	{
		return (Mathf.Pow(time-1,3) + 1);
    }
	function InOut (time : float) : float
	{
		return EasingHelper.InOut(this, time);
    }
}
	
// ---------------------------------------- //
// QUARTIC EASING

class QuarticEasing extends AnimationEasing
{
	function In (time : float)
	{
		return Mathf.Pow(time,4);
    }
	function Out (time : float)
	{
		return (Mathf.Pow(time-1,4) - 1) * -1;
    }
	function InOut (time : float) : float
	{
		return EasingHelper.InOut(this, time);
    }
}

// ---------------------------------------- //
// QUINTIC EASING

class QuinticEasing extends AnimationEasing
{
	function In (time : float)
	{
		return Mathf.Pow(time,5);
    }
	function Out (time : float)
	{
		return (Mathf.Pow(time-1,5) + 1);
    }
	function InOut (time : float) : float
	{
		return EasingHelper.InOut(this, time);
    }
}
	
// ---------------------------------------- //
// SINUSOIDAL EASING

class SinusoidalEasing extends AnimationEasing
{
	function In (time : float)
	{
		return Mathf.Sin((time-1)*(Mathf.PI/2)) + 1;
    }
	function Out (time : float)
	{
		return Mathf.Sin(time*(Mathf.PI/2));
    }
	function InOut (time : float) : float
	{
		return EasingHelper.InOut(this, time);
    }
}
	
// ---------------------------------------- // 
// EXPONENTIAL EASING

class ExponentialEasing extends AnimationEasing
{
	function In (time : float)
	{
		return Mathf.Pow(2,10*(time-1));
    }
	function Out (time : float)
	{
		return (-1 * Mathf.Pow(2,-10*time) + 1);
    }
	function InOut (time : float) : float
	{
		return EasingHelper.InOut(this, time);
    }
}

// ---------------------------------------- //
// CIRCULAR EASING

class CircularEasing extends AnimationEasing
{
	function In (time : float)
	{
		return (-1 * Mathf.Sqrt(1 - time*time) + 1);
    }
	function Out (time : float)
	{
		return Mathf.Sqrt(1 - Mathf.Pow(time-1,2));
    }
	function InOut (time : float) : float
	{
		return EasingHelper.InOut(this, time);
    }
}